module nade.adt;

import std.stdio;
import std.string;
import std.conv;


alias Element[Variable] Environment;


class Element {
	bool match(Element rhs, ref Environment env)
	{
		return rhs is this;
	}

	Element dup(Sequence tail = null)
	{
		return this;
	}

	Sequence opCat(Element rhs)
	{
		return sequence(this) ~ rhs;
	}
}


class Symbol : Element {
	this()
	{
		_name = "_anonymous_";
	}
	private this(string name)
	{
		_name = name;
	}

	override string toString()
	{
		return _name;
	}

	private {
		string		_name;
	}
	static {
		Symbol[string]		_named;

		Symbol get(string name)
		{
			auto p = name in _named;
			if(p is null) return _named[name] = new Symbol(name);
			return *p;
		}
	}
}
alias Symbol.get sym;


class Character : Element {
	private this(char value)
	{
		_value = value;
	}

	String cat(Character rhs)
	{
		return new String("" ~ _value ~ rhs._value);
	}
	String cat(String rhs)
	{
		return new String(_value ~ rhs._value);
	}

	override string toString() { return to!(string)(_value); }

	char value() { return _value; }

	private {
		char		_value;
	}
	static {
		Character[256]		_characters;

		static this()
		{
			foreach(c, ref character; _characters)
				character = new Character(c);
		}

		Character get(char c)
		{
			return _characters[c];
		}
	}
}
alias Character.get chr;


String cat(string lhs, String rhs)
{
	return new String(lhs ~ rhs.value);
}


class String : Element {
	this(char c)
	{
		_value = to!(string)(c);
	}
	this(Element element)
	{
		auto character = cast(Character)element;
		if(character !is null)
		{
			_value = character.toString;
		}
		else
		{
			assert(false);
		}
	}
	this(string value)
	{
		_value = value;
	}

	override bool match(Element rhs, ref Environment env)
	{
		if(this is rhs) return true;
		auto srhs = cast(String)rhs;
		if(srhs is null) return false;
		return _value == srhs._value;
	}

	String cat(Character rhs)
	{
		return new String(_value ~ rhs._value);
	}
	String cat(String rhs)
	{
		return new String(_value ~ rhs._value);
	}
	String cat(string rhs)
	{
		return new String(_value ~ rhs);
	}

	override string toString() { return '"' ~ _value ~ '"'; }

	string value() { return _value; }

	private {
		string		_value;
	}
}


class Integer : Element {
	this(long value)
	{
		_value = value;
	}
	this(string value)
	{
		_value = to!(long)(value);
	}

	override bool match(Element rhs, ref Environment env)
	{
		if(this is rhs) return true;
		auto srhs = cast(Integer)rhs;
		if(srhs is null) return false;
		return _value == srhs._value;
	}

	Integer opAdd(long rhs) { return new Integer(_value + rhs); }
	Integer opSub(long rhs) { return new Integer(_value - rhs); }
	Integer opMul(long rhs) { return new Integer(_value * rhs); }
	Integer opDiv(long rhs) { return new Integer(_value / rhs); }
	Integer opAdd(Integer rhs) { return new Integer(_value + rhs._value); }
	Integer opSub(Integer rhs) { return new Integer(_value - rhs._value); }
	Integer opMul(Integer rhs) { return new Integer(_value * rhs._value); }
	Integer opDiv(Integer rhs) { return new Integer(_value / rhs._value); }
	Integer opNeg() { return new Integer(-_value); }

	override string toString() { return format("%d", _value); }

	long value() { return _value; }

	private {
		long		_value;
	}
}


class Sequence : Element {
	this(Element car = null, Sequence cdr = null)
	{
		_car = car;
		_cdr = cdr;
	}

	override bool match(Element rhs, ref Environment env)
	{
		if(_car.match(rhs, env)) return true;
		if(_cdr is null) return false;
		return _cdr.match(rhs, env);
	}

	override Sequence dup(Sequence tail = null)
	{
		return new Sequence(_car, (_cdr is null) ? tail : _cdr.dup(tail));
	}

	Element opIndex(uint i)
	{
		if(i > 0) return (_cdr is null) ? null : _cdr[i - 1];
		return _car;
	}
	Element opIndexAssign(Element rhs, uint i)
	{
		if(i > 0) return (_cdr is null) ? null : (_cdr[i - 1] = rhs);
		return _car = rhs;
	}

	int opApply(int delegate(ref Element) dg)
	{
		const r = dg(_car);
		if(r != 0) return r;
		if(_cdr is null) return 0;
		return _cdr.opApply(dg);
	}

	override Sequence opCat(Element rhs)
	{
		auto srhs = cast(Sequence)rhs;
		if(srhs !is null) return dup(srhs);
		if(rhs is null) return this;
		return dup(new Sequence(rhs));
	}

	override string toString()
	{
		Element prev = null;
		string r;
		foreach(element; this)
		{
			bool pc = cast(Character)prev !is null;
			bool ec = cast(Character)element !is null;
			if(prev !is null  &&  !pc) r ~= " ";
			if(pc  &&  !ec) r ~= "'";
			if(!pc  &&  ec) r ~= "'";
			r ~= element.toString;
			prev = element;
		}
		if(cast(Character)prev !is null) r ~= "'";
		return "(" ~ r ~ ")";
	}

	Element head() { return _car; }
	Sequence tail() { return _cdr; }

	private {
		Element		_car = null;
		Sequence	_cdr = null;
	}

	static {
		Sequence create(string s)
		{
			Sequence r = null;
			foreach_reverse(c; s)
				r = new Sequence(chr(c), r);
			return r;
		}
		Sequence create(Element[] elements ...)
		{
			Sequence r = null;
			foreach_reverse(element; elements)
				r = new Sequence(element, r);
			return r;
		}

		T capture(T)(ref Sequence input)
		{
			assert(input !is null);
			auto r = cast(T)input.head;
			assert(r !is null, T.classinfo.name ~ ", "~ input.head.classinfo.name);
			input = input.tail;
			return r;
		}
	}
}
alias Sequence.create sequence;
alias Sequence.capture capture;


class Expression : Element {
	this(Sequence delegate() dg)
	{
		_dg = dg;
	}

	Sequence opCall()
	{
		return _dg();
	}

	private {
		Sequence delegate()	_dg;
	}
}
Expression expr(Sequence delegate() dg)
{
	return new Expression(dg);
}


class Variable : Element {
	private this(string name)
	{
		_name = name;
	}

	override bool match(Element rhs, ref Environment env)
	{
		env[this] = rhs;
		return true;
	}

	Sequence opCall()
	{
		return null;
	}

	override string toString()
	{
		return ":" ~ _name;
	}

	private {
		string		_name;
	}
	static {
		Variable[string]		_named;

		Variable get(string name)
		{
			auto p = name in _named;
			if(p is null) return _named[name] = new Variable(name);
			return *p;
		}
	}
}
alias Variable.get var;
